using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Linq;

namespace NativeWiFiLibrary
{
    internal class WlanManager : IDisposable
    {
        private IntPtr clientHandle;

        private struct GetAvailableNetworkFlags
        {
            public const int IncludeAdHocProfiles  = 1;
            public const int IncludeHiddenProfiles = 2;
        }

        internal WlanManager()
        {
            OpenHandle();
        }

        /// <summary>
        /// Loads the specified number of items of the specified structure type from the specified memory offset into an array.
        /// </summary>
        private static T[] LoadStructureArray<T>(long numberOfItems, long memoryOffset)
        {
            T[] array = (T[])Array.CreateInstance(typeof(T), numberOfItems);
            int dataSize = Marshal.SizeOf(typeof(T));
            for (long i = 0, ptr = memoryOffset; i < array.Length; i++, ptr += dataSize)
            {
                array.SetValue(Marshal.PtrToStructure(new IntPtr(ptr), typeof(T)), i);
            }
            return array;
        }

        public void Dispose()
        {
            try
            {
                CloseHandle();
            }
            catch (WlanException)
            {
                // Ignore
            }
        }
        private void OpenHandle()
        {
            Int32 negotiatedVersion;
            uint result = WlanApi.WlanOpenHandle(WlanApi.WLAN_API_VERSION_2_0, 0, out negotiatedVersion, out clientHandle);
            if (result != 0)
            {
                throw new WlanException(result);
            }
        }
        private void CloseHandle()
        {
            uint result = WlanApi.WlanCloseHandle(clientHandle, 0);
            if (result != 0)
            {
                throw new WlanException(result);
            }
        }
        private void FreeMemory(IntPtr pMemory)
        {
            if (pMemory != IntPtr.Zero)
            {
                WlanApi.WlanFreeMemory(pMemory);
            }
        }

        internal void Disconnect(Guid interfaceGuid)
        {
            uint result = WlanApi.WlanDisconnect(clientHandle, ref interfaceGuid, 0);
            if (result != 0)
            {
                throw new WlanException(result);
            }
        }

        internal string GetProfile(Guid interfaceGuid, string profileName)
        {
            uint flags = 0, grantedAccess = 0;
            string profileXml = null;
            uint result = WlanApi.WlanGetProfile(clientHandle, ref interfaceGuid, profileName, IntPtr.Zero, ref profileXml, ref flags, ref grantedAccess);
            if (result != 0)
            {
                throw new WlanException(result);
            }

            return profileXml;
        }

        internal void ConnectByProfile(Guid interfaceGuid, string profileName)
        {
            WlanApi.WLAN_CONNECTION_PARAMETERS wlanConnectionParameters = new WlanApi.WLAN_CONNECTION_PARAMETERS
                {
                    dot11BssType = WlanApi.DOT11_BSS_TYPE.dot11_BSS_type_any,
                    dwFlags = 0,
                    strProfile = profileName,
                    pDesiredBssidList = IntPtr.Zero,
                    pDot11Ssid = IntPtr.Zero,
                    wlanConnectionMode = WlanApi.WLAN_CONNECTION_MODE.wlan_connection_mode_profile
                };

            uint result = WlanApi.WlanConnect(clientHandle, ref interfaceGuid, ref wlanConnectionParameters, IntPtr.Zero);
            if (result != 0)
            {
                throw new WlanException(result);
            }
        }

        internal void Connect(Guid interfaceGuid, string ssid, BssType bssType, AuthenticationAlgorithm authenticationAlgorithm, CipherAlgorithm cipherAlgorithm)
        {
            WlanApi.DOT11_SSID dot11Ssid = new WlanApi.DOT11_SSID { SSID = ssid, SSIDLength = ssid.Length };

            IntPtr pDot11Ssid = Marshal.AllocHGlobal(Marshal.SizeOf(dot11Ssid));
            Marshal.StructureToPtr(dot11Ssid, pDot11Ssid, false);

            string authentication, encryption;
            switch (authenticationAlgorithm)
            {
                case AuthenticationAlgorithm._80211SharedKey:
                    authentication = "shared";
                    break;
                case AuthenticationAlgorithm.Wpa:
                    authentication = "WPA";
                    break;
                case AuthenticationAlgorithm.WpaPsk:
                    authentication = "WPAPSK";
                    break;
                case AuthenticationAlgorithm.Rsna:
                    authentication = "WPA2";
                    break;
                case AuthenticationAlgorithm.RsnaPsk:
                    authentication = "WPA2PSK";
                    break;
                default:
                    authentication = "open";
                    break;
            }

            switch (cipherAlgorithm)
            {
                case CipherAlgorithm.Wep:
                case CipherAlgorithm.Wep104:
                case CipherAlgorithm.Wep40:
                    encryption = "WEP";
                    break;
                case CipherAlgorithm.Tkip:
                    encryption = "TKIP";
                    break;
                case CipherAlgorithm.WpaUseGroup:
                case CipherAlgorithm.Ccmp:
                    encryption = "AES";
                    break;
                default:
                    encryption = "none";
                    break;
            }

            string profileXml = string.Format(@"<?xml version=""1.0""?>
<WLANProfile xmlns=""http://www.microsoft.com/networking/WLAN/profile/v1"">
	<name>Temp{0}</name>
	<SSIDConfig><SSID><name>{0}</name></SSID></SSIDConfig>
    <connectionType>{1}</connectionType>
    <connectionMode>auto</connectionMode>
	<MSM><security><authEncryption><authentication>{3}</authentication><encryption>{2}</encryption></authEncryption></security></MSM>
</WLANProfile>
",
    ssid,
    bssType == BssType.Independent ? "IBSS" : "ESS",
    encryption,
    authentication);

            WlanApi.WLAN_CONNECTION_PARAMETERS wlanConnectionParameters = new WlanApi.WLAN_CONNECTION_PARAMETERS
            {
                dot11BssType = WlanApi.DOT11_BSS_TYPE.dot11_BSS_type_any,
                dwFlags = 0,
                strProfile = profileXml,
                pDot11Ssid = pDot11Ssid,
                wlanConnectionMode = WlanApi.WLAN_CONNECTION_MODE.wlan_connection_mode_temporary_profile
            };

            uint result = WlanApi.WlanConnect(clientHandle, ref interfaceGuid, ref wlanConnectionParameters, IntPtr.Zero);
            if (result != 0)
            {
                throw new WlanException(result);
            }

            Marshal.FreeHGlobal(pDot11Ssid);
        }

        internal void WlanDeleteProfile(Guid interfaceGuid, string profileName)
        {
            uint result = WlanApi.WlanDeleteProfile(clientHandle, ref interfaceGuid, profileName, IntPtr.Zero);
            if (result != 0)
            {
                throw new WlanException(result);
            }
        }

        internal static void WlanUIEditProfile(Guid interfaceGuid, string profileName)
        {
            int pWlanReasonCode = 0;
            uint result = WlanApi.WlanUIEditProfile(WlanApi.WLAN_UI_API_VERSION, profileName, ref interfaceGuid, IntPtr.Zero, WlanApi.WL_DISPLAY_PAGES.WLConnectionPage, IntPtr.Zero, ref pWlanReasonCode);
            if (result != 0)
            {
                throw new WlanException(result);
            }
        }

        internal List<NetworkDevice> GetNetworkDevices()
        {
            IntPtr ptr;
            uint result = WlanApi.WlanEnumInterfaces(clientHandle, 0, out ptr);
            try
            {
                if (result != 0)
                {
                    throw new WlanException(result);
                }

                WlanApi.WlanInterfaceInfoList interfaceList = (WlanApi.WlanInterfaceInfoList)Marshal.PtrToStructure(ptr, typeof(WlanApi.WlanInterfaceInfoList));
                WlanApi.WlanInterfaceInfo[] interfaces = LoadStructureArray<WlanApi.WlanInterfaceInfo>(interfaceList.dwNumberOfItems, (long)ptr + 2 * sizeof(uint));

                // Translate types
                return interfaces.ToList().ConvertAll<NetworkDevice>(o => NetworkDevice.FromWlanApi(o));
            }
            finally
            {
                Marshal.FreeHGlobal(ptr);
            }
        }

        internal List<Network> GetAvailableNetworks(Guid interfaceGuid)
        {
            IntPtr ptr;
            uint result = WlanApi.WlanGetAvailableNetworkList(clientHandle, ref interfaceGuid, GetAvailableNetworkFlags.IncludeAdHocProfiles | GetAvailableNetworkFlags.IncludeHiddenProfiles, 0, out ptr);
            try
            {
                if (result != 0)
                {
                    throw new WlanException(result);
                }

                WlanApi.WlanAvailableNetworkList availableNetworkList = (WlanApi.WlanAvailableNetworkList)Marshal.PtrToStructure(ptr, typeof(WlanApi.WlanAvailableNetworkList));
                WlanApi.WlanAvailableNetwork[] networks = LoadStructureArray<WlanApi.WlanAvailableNetwork>(availableNetworkList.dwNumberOfItems, (long)ptr + 2 * sizeof(uint));

                // Translate types
                return networks.ToList().ConvertAll<Network>(o => Network.FromWlanApi(interfaceGuid, o));
            }
            finally
            {
                FreeMemory(ptr);
            }
        }

        /// <summary>
        /// http://msdn2.microsoft.com/en-us/library/ms706783.aspx
        /// </summary>
        /// <param name="interfaceGuid"></param>
        internal void Scan(Guid interfaceGuid)
        {
            IntPtr ptr;
            uint result = WlanApi.WlanScan(clientHandle, ref interfaceGuid, IntPtr.Zero, out ptr, 0);
            try
            {
                if (result != 0)
                {
                    throw new WlanException(result);
                }
            }
            finally
            {
                FreeMemory(ptr);
            }
        }

        internal List<AccessPoint> GetAccessPoints(Guid interfaceGuid)
        {
            return GetAccessPoints(interfaceGuid, null, BssType.Any, false);
        }

        /// <summary>
        /// http://msdn2.microsoft.com/en-us/library/ms706839.aspx
        /// </summary>
        /// <param name="interfaceGuid"></param>
        /// <returns></returns>
        internal List<AccessPoint> GetAccessPoints(Guid interfaceGuid, string ssid, BssType bssType, bool isSecurityEnabled)
        {
            IntPtr ptr;
            IntPtr pSSID = IntPtr.Zero;
            WlanApi.DOT11_SSID ssidStruct;

            if (ssid != null)
            {
                ssidStruct.SSID = ssid;
                ssidStruct.SSIDLength = ssid.Length;
                pSSID = Marshal.AllocHGlobal(Marshal.SizeOf(ssidStruct));
                Marshal.StructureToPtr(ssidStruct, pSSID, true);
            }

            uint result = WlanApi.WlanGetNetworkBssList(clientHandle, ref interfaceGuid, pSSID, (WlanApi.DOT11_BSS_TYPE)bssType, isSecurityEnabled, 0, out ptr);

            try
            {
                if (result != 0)
                {
                    throw new WlanException(result);
                }

                WlanApi.WLAN_BSS_LIST bssList = (WlanApi.WLAN_BSS_LIST)Marshal.PtrToStructure(ptr, typeof(WlanApi.WLAN_BSS_LIST));
                WlanApi.WLAN_BSS_ENTRY[] entries = LoadStructureArray<WlanApi.WLAN_BSS_ENTRY>(bssList.dwNumberOfItems, (long)ptr + 2 * sizeof(uint));

                // Translate types
                return entries.ToList().ConvertAll<AccessPoint>(o => AccessPoint.FromWlanApi(o));
            }
            finally
            {
                FreeMemory(ptr);

                if (pSSID != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(pSSID);
                }
            }
        }
    }
}
